package com.lechi.web.server;

import com.lechi.iot.android.channel.IIotTcpChannel;
import com.lechi.iot.android.channel.IotTcpChannelGroup;
import com.lechi.iot.android.device.IotTcpControlCentre;
import com.lechi.iot.android.device.IotTcpDeviceChannel;
import com.lechi.iot.netty.handle.TcpExceptionHandler;
import com.lechi.web.server.connect.IotTcpServerConnect;
import com.lechi.web.server.handle.AndroidInboundMessageHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.json.JsonObjectDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 处理Android报文的通讯服务端
 */
@Slf4j
public class AppServer extends ChannelInitializer<SocketChannel> implements IotTcpControlCentre {

    private static int IDLE_TIMEOUT = 60000;
    private final int port;
    private final int workThread;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private ChannelFuture future;
    @Getter
    private IotTcpChannelGroup groups;

    /**
     * 创建指定服务端口，指定线程数的服务端
     *
     * @param port          服务端口
     * @param workThread    执行线程池线程数
     * @param heartBeatTime 心跳检测超时时间(单位：毫秒)
     */
    public AppServer(int port, int workThread, int heartBeatTime) {
        this.port = port;
        this.workThread = workThread;
        IDLE_TIMEOUT = heartBeatTime;
        this.groups = new IotTcpChannelGroup();
    }

    /**
     * 启动服务
     */
    public void start() {
        bossGroup = new NioEventLoopGroup(1);
        workerGroup = new NioEventLoopGroup(workThread);
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childOption(ChannelOption.SO_LINGER, 0)
                .childHandler(this);
        future = b.bind(port);
    }

    /**
     * 停止服务
     */
    public void stop() {
        future.channel().closeFuture();
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }

    /**
     * 列出所有已连接设备组清单
     *
     * @return 所有已连接身边组清单
     */
    public List<IIotTcpChannel> groups() {
        return groups.list();
    }

    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline channelPipeline = ch.pipeline();
        IIotTcpChannel deviceChannel = new IotTcpDeviceChannel(ch);
        add(deviceChannel);
        channelPipeline.addLast(new IdleStateHandler(0, 0, IDLE_TIMEOUT, TimeUnit.SECONDS));
        channelPipeline.addLast(new ChannelInboundHandlerAdapter() {
            @Override
            public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                if (evt instanceof IdleStateEvent) {
                    log.error("APP服务端Socket服务指定时间 {} 范围内没有读写操作，断开连接， ：", IDLE_TIMEOUT);
                    ctx.disconnect();
                } else {
                    super.userEventTriggered(ctx, evt);
                }
            }
        });
        channelPipeline.addLast(new StringEncoder());
        channelPipeline.addLast(new JsonObjectDecoder());
        channelPipeline.addLast(new AndroidInboundMessageHandler());
        channelPipeline.addLast(new IotTcpServerConnect());
        channelPipeline.addLast(new TcpExceptionHandler());
    }

    @Override
    public boolean add(IIotTcpChannel channel) {
        return groups.add(channel);
    }

    @Override
    public IIotTcpChannel remove(String name) {
        return groups.remove(name);
    }

    @Override
    public IIotTcpChannel get(String name) {
        return groups.get(name);
    }

}
